// Copyright (c) 2025, 大连理工大学 (Dalian University of Technology)
//
// Licensed under the Mulan PSL v2.
// You can use this file according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of the License at
//
//     http://license.coscl.org.cn/MulanPSL2
//
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. SEE THE MULAN PSL v2
// FOR MORE DETAILS.

#include "DBManager/Database/HDF5Database.h"

#include <cstddef>
#include <fstream>
#include <map>
#include <memory>
#include <string>
#include <typeinfo>
#include <utility>

// 平台特定的头文件
#ifdef _WIN32
#include <windows.h>
#endif

#include "highfive/H5File.hpp"
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>

#include "Utility/Logging/SGLogger.h"

#include "DataStructure/Common/Status.h"

#include "DBManager/Database/DatabaseFactory.h"
#include "DBManager/Database/IDatabase.h"

#include "HDF5Database.h"

#if defined(SG_USE_MPI) && defined(H5_HAVE_PARALLEL)
#include "Utility/Algebra/MpiCommunicator.h"
#include "Utility/Algebra/MpiEnvironment.h"
#endif

using namespace SG::DBManager;
using namespace SG::DataStructure::FEM;

/**
 * @brief 获取系统总内存大小（字节）
 * @return 系统总内存大小，失败时返回默认值（8GB）
 * @note 跨平台实现：Windows使用GlobalMemoryStatusEx，Linux使用/proc/meminfo
 * @author wuxingchuang
 * @date 2025.08.22
 */
static std::size_t getSystemTotalMemory ()
{
#ifdef _WIN32
    // Windows 平台实现
    MEMORYSTATUSEX memStatus;
    memStatus.dwLength = sizeof (memStatus);

    if (GlobalMemoryStatusEx (&memStatus))
    {
        std::size_t totalMemBytes = static_cast<std::size_t> (memStatus.ullTotalPhys);
        SGTrace << "System total memory: " << (totalMemBytes / (1024 * 1024)) << " MB \n";
        return totalMemBytes;
    }
    else
    {
        SGTrace << "Failed to get Windows memory status, using default 8GB memory \n";
        return 8ULL * 1024 * 1024 * 1024;  // 默认8GB
    }
#else
    // Linux/Unix 平台实现
    // 使用Boost.Filesystem检查文件是否存在
    if (!boost::filesystem::exists ("/proc/meminfo"))
    {
        SGTrace << "/proc/meminfo does not exist, using default 8GB memory \n";
        return 8ULL * 1024 * 1024 * 1024;  // 默认8GB
    }

    std::ifstream meminfo ("/proc/meminfo");
    std::string   line;

    if (!meminfo.is_open ())
    {
        SGTrace << "Failed to open /proc/meminfo, using default 8GB memory \n";
        return 8ULL * 1024 * 1024 * 1024;  // 默认8GB
    }

    while (std::getline (meminfo, line))
    {
        if (line.find ("MemTotal:") == 0)
        {
            std::size_t pos = line.find (':');
            if (pos != std::string::npos)
            {
                std::string sizeStr = line.substr (pos + 1);
                // 移除空格
                sizeStr.erase (0, sizeStr.find_first_not_of (" \t"));
                sizeStr.erase (sizeStr.find_last_not_of (" \t") + 1);

                // 提取数字部分（去掉kB单位）
                std::size_t spacePos = sizeStr.find (' ');
                if (spacePos != std::string::npos)
                {
                    sizeStr = sizeStr.substr (0, spacePos);
                }

                try
                {
                    std::size_t memKB    = std::stoull (sizeStr);
                    std::size_t memBytes = memKB * 1024;  // 转换为字节
                    SGTrace << "System total memory: " << (memBytes / (1024 * 1024)) << " MB \n";
                    return memBytes;
                }
                catch (const std::exception& e)
                {
                    SGTrace << "Failed to parse memory size: " << e.what () << '\n';
                }
            }
            break;
        }
    }

    SGTrace << "MemTotal not found in /proc/meminfo, using default 8GB memory \n";
    return 8ULL * 1024 * 1024 * 1024;  // 默认8GB
#endif
}

std::vector<HDF5Hub::GetP2EFun>*                                              HDF5Hub::s_getP2EFunList           = nullptr;
std::map<ElementTypes, HDF5Hub::FunGetElementNodes>*                          HDF5Hub::s_getElementNodesFunMap   = nullptr;
std::map<SG::DataStructure::FEM::ElementTypes, HDF5Hub::FunInitElementRepos>* HDF5Hub::s_initElementReposMethods = nullptr;

/**
 * @brief    判断某文件是否为合法的HDF5文件
 *  在此函数内先禁用了HDF5自带的日志输出，然后恢复。
 * @param    filename
 * @return
 * @return
 * @author   wuxingchuang
 * @date     2025.04.01
 */
static bool isValidHDF5File (std::string filename)
{
    H5E_auto2_t old_func;
    void*       old_client_data;
    H5Eget_auto (H5E_DEFAULT, &old_func, &old_client_data);

    bool result = false;
    try
    {
        // 禁用日志输出
        H5Eset_auto (H5E_DEFAULT, NULL, NULL);
        HighFive::File (filename, HighFive::File::ReadOnly);
        result = true;  // 如果成功打开，则为有效的HDF5文件
    }
    catch (const std::exception& e)
    {
        SGTrace << "Create new DB block!" << '\n';
        SGTrace << e.what () << '\n';
        result = false;
    }

    // 恢复日志输出
    H5Eset_auto (H5E_DEFAULT, old_func, old_client_data);
    return result;
}

/**
 * @brief 构造函数，使用路径和文件名初始化 HDF5 数据库文件。
 * @param foldPath 数据库文件的路径。
 * @param fileName 数据库文件的名称。
 */
HDF5Database::HDF5Database (std::string foldPath, std::string fileName)
    : IDatabase (DatabaseFactory::HDF5, foldPath, std::move (fileName)),
      m_cachePool (static_cast<std::size_t> (getSystemTotalMemory () * 0.15),
                   std::chrono::milliseconds (60000))  // 初始化缓存池：系统内存的15%，30秒清理间隔
{
    // 如果不存在目录进行创建
    if (!boost::filesystem::exists (foldPath))
    {
        boost::filesystem::create_directories (foldPath);
    }

    // MPI-IO requires informing HDF5 that we want something other than
    // the default behaviour. This is done through property lists. We
    // need a file access property list.
    HighFive::FileAccessProps fapl;

#if defined(SG_USE_MPI) && defined(H5_HAVE_PARALLEL)
    int flag;
    MPI_Initialized (&flag);  // 检查是否已经初始化

    int commRank;
    MPI_Comm_rank (MPI_COMM_WORLD, &commRank);
    // 如果已经初始化，可以认为是主程序并行运行，这时候并行使用数据库
    if (flag != 0)
    {
        // MPI_Comm_size (MPI_COMM_WORLD, &commSize);

        // We tell HDF5 to use MPI-IO
        fapl.add (HighFive::MPIOFileAccess{ MPI_COMM_SELF, MPI_INFO_NULL });

        // We also specify that we want all meta-data related operations
        // to use MPI collective operations. This implies that all MPI ranks
        // in the communicator must participate in any HDF5 operation that
        // reads or writes metadata. Essentially, this is safe if all MPI ranks
        // participate in all HDF5 operations.
        fapl.add (HighFive::MPIOCollectiveMetadata{});
    }
#endif

    if (boost::filesystem::exists (GetFullpath ()) && isValidHDF5File (GetFullpath ()))
    {
        m_FilePtr = new HighFive::File (GetFullpath (), HighFive::File::OpenOrCreate, fapl);
    }
    else
    {
        m_FilePtr = new HighFive::File (GetFullpath (), HighFive::File::Truncate, fapl);
    }

    isConnected = true;

    // 初始化 HDF5 IDatabaseHub 对象
    m_hubptr = new HDF5Hub ();

    // 启动缓存池清理线程
    m_cachePool.StartCleanupThread ();

    const std::size_t totalMem = getSystemTotalMemory ();
    const std::size_t cacheMem = static_cast<std::size_t> (totalMem * 0.15);
    SGTrace << "HDF5Database: Initialized with cache pool (15% of system memory: " << (cacheMem / (1024 * 1024))
            << " MB, total memory: " << (totalMem / (1024 * 1024)) << " MB)" << '\n';
}

/**
 * @brief 析构函数，清理数据存储库并释放资源。
 */
HDF5Database::~HDF5Database ()
{
    // 停止缓存池清理线程
    m_cachePool.StopCleanupThread ();

    GetDataRepositories ().clear ();
    isConnected = false;

    delete m_FilePtr;
    m_FilePtr = nullptr;

    if (m_hubptr != nullptr)
    {
        delete ( HDF5Hub* ) m_hubptr;
        m_hubptr = nullptr;
    }
}

/**
 * @brief 清除数据库并重置 HDF5 文件。
 */
void HDF5Database::ClearDatabase ()
{
    m_hubptr->ClearSignal ();

    // 清空缓存池
    m_cachePool.Clear ();

    m_FilePtr->flush ();
    GetDataRepositories ().clear ();
    delete m_FilePtr;
    m_FilePtr = nullptr;

    m_FilePtr = new HighFive::File (GetFullpath (), HighFive::File::Truncate);
}

/**
 * @brief 保存数据库并以读写模式重新打开文件。
 */
void HDF5Database::SaveDatabase ()
{
    m_hubptr->ClearSignal ();

    GetDataRepositories ().clear ();
    m_FilePtr->flush ();
    delete m_FilePtr;
    m_FilePtr = nullptr;

    m_FilePtr = new HighFive::File (GetFullpath (), HighFive::File::ReadWrite);
}

void HDF5Database::Disconnect ()
{
    if (isConnected)
    {
        m_hubptr->ClearSignal ();

        // 清空缓存池
        m_cachePool.Clear ();
        GetDataRepositories ().clear ();
        m_FilePtr->flush ();
        delete m_FilePtr;
        m_FilePtr = nullptr;

        isConnected = false;
    }
}

void HDF5Database::Connect ()
{
    if (!isConnected)
    {
        m_FilePtr   = new HighFive::File (GetFullpath (), HighFive::File::ReadWrite);
        isConnected = true;
    }
}

/**
 * @brief 创建特定类型的数据存储库。
 * @param typeInfo 存储库类型的信息。
 * @return 如果创建成功返回 true，否则抛出异常。
 */
SG::DataStructure::Common::Status HDF5Database::CreateRepository (const std::type_info& typeInfo)
{
    // 获取类型的哈希值，用作唯一标识符
    const std::size_t typeID = typeInfo.hash_code ();

    // 在函数指针映射表中查找与该类型对应的函数指针
    auto iterFunP = GetFunPInfo ().find (typeID);

    // 如果找不到对应的函数指针，抛出异常并返回 false
    if (iterFunP == GetFunPInfo ().end ())
    {
        throw std::logic_error (std::string (typeInfo.name ()) + " do not support");
        // return SG::DataStructure::Common::Status::STATUS_Failure;
    }

    // 调用存储的 NewFunp 函数指针，创建新存储库对象
    void* rawPtr = iterFunP->second.NewFunp (*this);

    // 使用存储的 DelFunp 函数指针创建一个 shared_ptr，将其存入 GetDataRepositories() 映射表
    GetDataRepositories ()[typeID] = std::shared_ptr<void> (rawPtr, iterFunP->second.DelFunp);

    // 成功创建并存储存储库，返回 true
    return SG::DataStructure::Common::Status::STATUS_Success;
}

/**
 * @brief 注册存储库的创建与销毁函数指针。
 * @param key 存储库类型的哈希值。
 * @param value 包含创建与销毁函数指针的结构体。
 * @return 注册成功返回 true。
 */
bool HDF5Database::RegRepoFunP (std::size_t key, RepoFunPInfo value)
{
    GetFunPInfo ()[key] = value;
    return true;
}

/**
 * @brief 获取存储库函数指针的映射表。
 * @return 存储库函数指针的映射表。
 */
std::map<std::size_t, HDF5Database::RepoFunPInfo>& HDF5Database::GetFunPInfo ()
{
    static std::map<std::size_t, RepoFunPInfo> FunPInfo;
    return FunPInfo;
}

void HDF5Hub::ClearSignal ()
{
    m_elementSummarySignal.disconnect_all_slots ();
    m_coordinateSummarySignal.disconnect_all_slots ();
    m_materialSummarySignal.disconnect_all_slots ();
    m_loadSummarySignal.disconnect_all_slots ();
    m_constraintSummarySignal.disconnect_all_slots ();
    m_rElementSummarySignal.disconnect_all_slots ();
    m_dynamicSummarySignal.disconnect_all_slots ();
    m_propertySummarySignal.disconnect_all_slots ();
    m_tableSummarySignal.disconnect_all_slots ();

    IDatabaseHub::ClearSignal ();
}

/**
 * @brief 获取 HDF5 文件的共享指针。
 * @return 共享指向 HDF5 文件的指针。
 */
HighFive::File* HDF5Database::GetFile () const
{
    return m_FilePtr;
}
